﻿using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Data.SqlClient;
using System.IO;
using System.Linq;
using System.Management;
using System.Reflection;
using System.ServiceProcess;
using System.Text;
using System.Threading.Tasks;

namespace Amie
{
    public class ProductInfo
    {
        public ProductInfo(string webApplicationName, string webApplicationPoolName, string webAssemblyName, string serviceName, string serviceAssemblyName, string connectionStringName)
        {
            this.WebApplicationName = webApplicationName;
            this.WebApplicationPoolName = webApplicationPoolName;
            this.WebAssemblyName = webAssemblyName;
            this.ServiceName = serviceName;
            this.ServiceAssemblyName = serviceAssemblyName;
            this.ConnectionStringName = connectionStringName;
        }

        /// The Application name, for exampel UTA, not the website Name.
        /// </summary>
        public string WebApplicationName { get; set; }

        /// <summary>
        /// The applicaiton pool name in iis. this will be created if it does not exist or used if it does.
        /// </summary>
        public string WebApplicationPoolName { get; set; }

        /// <summary>
        /// The name of the executable for the website.  For example UTA.dll.  We use this to get the current version information.
        /// </summary>
        public string WebAssemblyName { get; set; }

        /// <summary>
        /// The connection string name used in the application configuration file.  This has to be the same in both the web and service config files.
        /// </summary>
        public string ConnectionStringName { get; set; }

        /// <summary>
        /// The Name of the service.  This name must be supplied and is used to find the current installed service version and running status.
        /// </summary>
        public string ServiceName { get; set; }

        /// <summary>
        /// The name of the service assembly, for example DiscoveryService.exe, this must be manually set. 
        /// </summary>
        public string ServiceAssemblyName { get; set; }

        /// <summary>
        /// The base update path to the Application directory
        /// </summary>
        [JsonIgnore]
        public string BaseUpdatePath
        {
            get
            {
                return Environment.CurrentDirectory + Path.DirectorySeparatorChar + "Application";
            }
        }

        /// <summary>
        /// The full update path to the web update files.
        /// </summary>
        [JsonIgnore]
        public string WebUpdatePath
        {
            get
            {
                return BaseUpdatePath + Path.DirectorySeparatorChar + "Web";
            }
        }

        /// <summary>
        /// The full update path to the service update files.
        /// </summary>
        [JsonIgnore]
        public string ServiceUpdatePath
        {
            get
            {
                return BaseUpdatePath + Path.DirectorySeparatorChar + "Service" + Path.DirectorySeparatorChar + this.ServiceAssemblyName;
            }
        }

        /// <summary>
        /// The full path to the service application servicename.exe.config file in the update directory.
        /// </summary>
        [JsonIgnore]
        public string ServiceUpdateConfigPath
        {
            get
            {
                return BaseUpdatePath + Path.DirectorySeparatorChar + "Service" + Path.DirectorySeparatorChar + this.ServiceAssemblyName + ".config";
            }
        }

        /// <summary>
        /// Returns the full path to the installed service configuration file.
        /// </summary>
        [JsonIgnore]
        public string ServiceInstalledConfigPath
        {
            get
            {
                if (this.ServiceInstalledPath == "") return "";
                string path = Path.GetDirectoryName(this.ServiceInstalledPath) + Path.DirectorySeparatorChar + this.ServiceAssemblyName + ".config";
                return path;
            }
        }

        /// <summary>
        /// The application exists on the server
        /// </summary>
        [JsonIgnore]
        public bool WebIsInstalled
        {
            get
            {
                return IISHelper.ApplicationExists(this.WebApplicationName);
            }
        }

        /// <summary>
        /// The name of the web site.  Most of the time this will be Default Web Site 
        /// </summary>
        [JsonIgnore] 
        public Microsoft.Web.Administration.Site WebSite
        {
            get
            {
                return IISHelper.GetSiteByApplicationName(this.WebApplicationName);
            }
        }

        /// <summary>
        /// The web application that matches the WebApplicationName.  If this is null the service does not exist.
        /// </summary>
        [JsonIgnore ]
        public Microsoft.Web.Administration.Application WebApplication
        {
            get
            {
                return IISHelper.GetApplication(WebSite.Name, this.WebApplicationName);
            }
        }

        /// <summary>
        /// The path where the web application is installed.  This is the physical path of the root of the application.  Not the bin folder.
        /// </summary>
        [JsonIgnore]
        public string WebInstalledPath
        {
            get
            {
                if (this.WebApplication == null) return "";
                return this.WebApplication.VirtualDirectories[0].PhysicalPath;
            }
        }

        /// <summary>
        /// The full path to the web application web.config that is currently installed.
        /// </summary>
        [JsonIgnore]
        public string WebInstalledConfigPath
        {
            get
            {
                if (this.WebInstalledPath == "") return "";
                return this.WebInstalledPath + Path.DirectorySeparatorChar + "web.config";
            }
        }

        /// <summary>
        /// The full path to the web application web.config file in the update directory.
        /// </summary>
        [JsonIgnore]
        public string WebUpdateConfigPath
        {
            get
            {
                if (this.WebUpdatePath == "") return "";
                return this.WebUpdatePath + Path.DirectorySeparatorChar + "web.config";
            }
        }

        /// <summary>
        /// The version of the installed web application
        /// </summary>
        [JsonIgnore]
        public Version WebInstalledVersion
        {
            get
            {
                if (this.WebInstalledPath == "") return new Version();
                var path = this.WebInstalledPath + Path.DirectorySeparatorChar + "bin" + Path.DirectorySeparatorChar + this.WebAssemblyName;
                return GetAssemblyVersion(path);
            }
        }

        /// <summary>
        /// Returns true if the Update Version > Installed Version, false other wise.
        /// </summary>
        [JsonIgnore]
        public bool WebNeedsUpdate
        {
            get
            {
                return WebInstalledVersion < WebUpdateVersion;
            }
        }

        /// <summary>
        /// The version of the web application file to be installed with this update.
        /// </summary>
        [JsonIgnore]
        public Version WebUpdateVersion
        {
            get
            {
                var path = this.WebUpdatePath + Path.DirectorySeparatorChar + "bin" + Path.DirectorySeparatorChar + this.WebAssemblyName;
                return GetAssemblyVersion(path);
            }
        }

        private Version GetAssemblyVersion(string path)
        {
            try
            {
                Assembly assembly = Assembly.LoadFrom(CleanPath(path));
                return assembly.GetName().Version;
            }
            catch (Exception)
            {
                return new Version();
            }
        }

        private string CleanPath(string path)
        {
            string invalid = new string(Path.GetInvalidPathChars());
            foreach (char c in invalid)
            {
                path = path.Replace(c.ToString(), "");
            }
            return path;
        }

        /// <summary>
        /// The version of the service to be installed with this update. 
        /// </summary>
        [JsonIgnore]
        public Version ServiceUpdateVersion
        {
            get
            {
                return GetAssemblyVersion(this.ServiceUpdatePath);
            }
        }

        /// <summary>
        /// The Path where the currently installed service is running.
        /// </summary>
        [JsonIgnore]
        public string ServiceInstalledPath
        {
            get
            {
                if (!ServiceIsInstalled) return "";

                try
                {
                    ManagementObject wmiService;
                    wmiService = new ManagementObject("Win32_Service.Name='" + this.ServiceName + "'");
                    wmiService.Get();
                    
                    // string name = wmiService["Name"] as string;
                    string pathName = wmiService["PathName"] as string;

                    FileInfo file = new FileInfo(CleanPath(pathName));
                    return file.FullName;
                    //return pathName;
                }
                catch (Exception)
                {
                    return "";
                }
            }
        }

        /// <summary>
        /// The version of the currently installed version of the service.
        /// </summary>
        [JsonIgnore]
        public Version ServiceInstalledVersion
        {
            get
            {
                return GetAssemblyVersion(ServiceInstalledPath);
            }
        }

        /// <summary>
        /// Returns true if the Service Name can be found on the computer.
        /// </summary>
        [JsonIgnore]
        public bool ServiceIsInstalled
        {
            get
            {

                return this.Service != null;
            }
        }

        /// <summary>
        /// Returns true if the Update Version > Installed Service Version, false other wise.
        /// </summary>
        [JsonIgnore]
        public bool ServiceNeedsUpdate
        {
            get
            {
                return ServiceUpdateVersion > ServiceInstalledVersion;
            }
        }

        /// <summary>
        /// Returns the service by looking up the ServiceName.  This can be null if the service is not installed.
        /// </summary>
        [JsonIgnore]
        public ServiceController Service
        {
            get
            {
                return ServiceController.GetServices().FirstOrDefault(s => s.ServiceName == this.ServiceName);
            }
        }

        SqlConnectionStringBuilder _conStr;
        /// <summary>
        /// The ConnectionString used in the service and web product.
        /// </summary>
        [JsonIgnore]
        public SqlConnectionStringBuilder ConnectionString
        {
            get
            {
                if (_conStr == null)
                {
                    ExeConfigurationFileMap configFile = new ExeConfigurationFileMap();
                    Configuration config;
                    //Check the installed path first so that the user does not have to change the connection string!!
                    if (WebInstalledConfigPath != "")
                    {
                        
                        configFile.ExeConfigFilename = this.WebInstalledConfigPath;
                        config = ConfigurationManager.OpenMappedExeConfiguration(configFile, ConfigurationUserLevel.None);
                    }
                    else
                    {
                        configFile.ExeConfigFilename = this.WebUpdateConfigPath;
                        config = ConfigurationManager.OpenMappedExeConfiguration(configFile, ConfigurationUserLevel.None);
                    }

                    if (!File.Exists(configFile.ExeConfigFilename)) return null;
                    _conStr = new SqlConnectionStringBuilder(config.ConnectionStrings.ConnectionStrings[this.ConnectionStringName].ConnectionString);
                }
                return _conStr;
            }
            set 
            {
                _conStr = value;
            }

        }

        /// <summary>
        /// The version of the database that is installed.
        /// </summary>
        [JsonIgnore]
        public string DatabaseInstalledVersion
        {
            get
            {
                var changeApi = new Amie.DB.SchemaChangeApi(this);
                return changeApi.DatabaseVersionInstalled.ToString();
            }
        }

        /// <summary>
        /// The update version of the database.
        /// </summary>
        [JsonIgnore]
        public string DatabaseUpdateVersion
        {
            get
            {
                var changeApi = new Amie.DB.SchemaChangeApi(this);
                return changeApi.DatabaseUpdateVersion.ToString();
            }
        }

        /// <summary>
        /// Returns true if the installed version and the update version numbers don't match exactally.  This does not look for greater or lesser just matching numbers.  
        /// </summary>
        [JsonIgnore]
        public bool DatabaseNeedsUpdate
        {
            get
            {
                return DatabaseInstalledVersion != DatabaseUpdateVersion;
            }
        }

        /// <summary>
        /// The full update path to the database sql update files.
        /// </summary>
        [JsonIgnore]
        public string DatabaseUpdatePath
        {
            get
            {
                return BaseUpdatePath + Path.DirectorySeparatorChar + "Database";
            }
        }

        /// <summary>
        /// Checks the web, service and database and returns true if any one of them needs to be udpated.
        /// </summary>
        [JsonIgnore]
        public bool UpdateRequired
        {
            get
            {
                if (this.WebNeedsUpdate) return true;
                if (this.ServiceNeedsUpdate) return true;
                if (this.DatabaseNeedsUpdate) return true;

                return false;
            }
        }
    }
}
